ggplotly() to the rescue
If you have plots in ggplot, you can start using plotly with just a simple function call on most ggplot objects.
Let’s make a ggplot from Propser data.
pros_scat = pros_df_sam%>%
ggplot(aes(principal_paid, interest_paid))+geom_point(aes(color = factor(prosper_rating)))
pros_scat

ggplotly(pros_scat)
NA
pros_df_sam%>%
plot_ly(x = ~principal_paid, y = ~interest_paid)%>%
add_markers(color = ~prosper_rating)
Do it again, but with lines:
amt_fin_p = pros_agg%>%
ggplot(aes(origination_date,avg_amt_borrowed))+
geom_line(aes(color = prosper_rating))
amt_fin_p

Simple ggplotly command adds the tooltip, zooming,
ggplotly(amt_fin_p)
Now the plotly syntax:
Same Graph different syntax (using the add_* trace addition)
pros_agg%>%
ungroup()%>%
plot_ly(x = ~origination_date, y = ~avg_amt_borrowed)%>%
add_lines(color = ~prosper_rating)
Histogram:
Histogram with muiltple factors
Now lets do this with 2 dimentions
subplt = subplot(
pros_df_sam%>%plot_ly(x = ~principal_paid, color = I("black"),type = 'histogram'),
plotly_empty(),
pros_df_sam%>%plot_ly(x = ~principal_paid,y = ~interest_paid,type = 'histogram2dcontour'),
pros_df_sam%>%plot_ly(y = ~interest_paid,color = I("black"),type = 'histogram'),
nrows = 2,
heights = c(0.2,0.8),
widths = c(0.8,0.2),
shareX = TRUE,
shareY = TRUE
)
No trace type specified and no positional attributes specifiedNo trace type specified:
Based on info supplied, a 'scatter' trace seems appropriate.
Read more about this trace type -> https://plot.ly/r/reference/#scatter
No scatter mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
p = layout(subplt, showlegend = FALSE)
p
NA
x <- rnorm(1000)
y <- rnorm(1000)
s <- subplot(
plot_ly(x = x, color = I("black"), type = 'histogram'),
plotly_empty(),
plot_ly(x = x, y = y, type = 'histogram2dcontour', showscale = F),
plot_ly(y = y, color = I("black"), type = 'histogram'),
nrows = 2, heights = c(0.2, 0.8), widths = c(0.8, 0.2),
shareX = TRUE, shareY = TRUE, titleX = FALSE, titleY = FALSE
)
No trace type specified and no positional attributes specifiedNo trace type specified:
Based on info supplied, a 'scatter' trace seems appropriate.
Read more about this trace type -> https://plot.ly/r/reference/#scatter
No scatter mode specifed:
Setting the mode to markers
Read more about this attribute -> https://plot.ly/r/reference/#scatter-mode
p <- layout(s, showlegend = FALSE)
p
What does that look like in ggplot? Doable but not intuitive.
library(gridExtra)
hist_top <- pros_df_sam%>%ggplot(aes(principal_paid))+geom_histogram()
empty <- ggplot()+geom_point(aes(1,1), colour="white")+
theme(axis.ticks=element_blank(),
panel.background=element_blank(),
axis.text.x=element_blank(), axis.text.y=element_blank(),
axis.title.x=element_blank(), axis.title.y=element_blank())
scatter <- ggplot()+geom_density_2d(aes(pros_df_sam$principal_balance, pros_df_sam$interest_paid))
hist_right <- pros_df_sam%>%ggplot(aes(interest_paid))+geom_histogram()+coord_flip()
grid.arrange(hist_top, empty, scatter, hist_right, ncol=2, nrow=2, widths=c(4, 1), heights=c(1, 4))

Ack. Not as good.
Now some plots that are very difficult to do in ggplot and rely on the interactivity heavily.
Sunburst
First step to making this one work is processing the data:
Must have labels, parents and values.
Will use the Titanic dataset because it has some nice categories and values to take on:
agg1 = titanic_df%>%
group_by(Survived)%>%
summarize(value = sum(Freq))%>%
mutate(id = as.character(Survived),
label = as.character(Survived),
parent = "")%>%
select(label, id, parent, value)
agg2 = titanic_df%>%
group_by(Survived,Class)%>%
summarize(value = sum(Freq))%>%
ungroup()%>%
mutate(id = paste(Survived, Class, sep = ' - '),
parent = as.character(Survived),
label = as.character(Class))%>%
select(label, id, parent, value)
agg3 = titanic_df%>%
group_by(Survived, Class, Age)%>%
summarize(value = sum(Freq))%>%
ungroup()%>%
mutate(id = paste(Survived, Class, Age, sep = ' - '),
parent = paste(Survived, Class, sep = ' - '),
label = as.character(Age))%>%
select(label, id, parent, value)
agg4 = titanic_df%>%
group_by(Survived,Class,Age,Sex)%>%
summarize(value = sum(Freq))%>%
ungroup()%>%
mutate(id = paste(Survived, Class, Age, Sex, sep = ' - '),
parent = paste(Survived, Class, Age, sep = ' - '),
label = as.character(Sex))%>%
select(label, id, parent, value)
agg =bind_rows(agg1,agg2)%>%bind_rows(agg3)%>%bind_rows(agg4)%>%filter(value > 0)
head(agg)
library(plotly)
d <- data.frame(
ids = c(
"North America", "Europe", "Australia", "North America - Football", "Soccer",
"North America - Rugby", "Europe - Football", "Rugby",
"Europe - American Football","Australia - Football", "Association",
"Australian Rules", "Autstralia - American Football", "Australia - Rugby",
"Rugby League", "Rugby Union"
),
labels = c(
"North<br>America", "Europe", "Australia", "Football", "Soccer", "Rugby",
"Football", "Rugby", "American<br>Football", "Football", "Association",
"Australian<br>Rules", "American<br>Football", "Rugby", "Rugby<br>League",
"Rugby<br>Union"
),
parents = c(
"", "", "", "North America", "North America", "North America", "Europe",
"Europe", "Europe","Australia", "Australia - Football", "Australia - Football",
"Australia - Football", "Australia - Football", "Australia - Rugby",
"Australia - Rugby"
),
stringsAsFactors = FALSE
)
plot_ly(d, ids = ~ids, labels = ~labels, parents = ~parents, type = 'sunburst')%>%
library(rjson)
json_file <- "https://raw.githubusercontent.com/plotly/plotly.js/master/test/image/mocks/sankey_energy.json"
json_data <- fromJSON(paste(readLines(json_file), collapse=""))
p <- plot_ly(
type = "sankey",
domain = list(
x = c(0,1),
y = c(0,1)
),
orientation = "h",
valueformat = ".0f",
valuesuffix = "TWh",
node = list(
label = json_data$data[[1]]$node$label,
color = json_data$data[[1]]$node$color,
pad = 15,
thickness = 15,
line = list(
color = "black",
width = 0.5
)
),
link = list(
source = json_data$data[[1]]$link$source,
target = json_data$data[[1]]$link$target,
value = json_data$data[[1]]$link$value,
label = json_data$data[[1]]$link$label
)
) %>%
layout(
title = "Energy forecast for 2050<br>Source: Department of Energy & Climate Change, Tom Counsell via <a href='https://bost.ocks.org/mike/sankey/'>Mike Bostock</a>",
font = list(
size = 10
),
xaxis = list(showgrid = F, zeroline = F),
yaxis = list(showgrid = F, zeroline = F)
)
p
LS0tDQp0aXRsZTogIlBsb3QubHk6IEhvdyBJIGxlYXJuZWQgYSAodmVyeSBsaXR0bGUpIGphdmEgc2NyaXB0IGFuZCBsb3ZlIGludGVyYWN0aXZlIHBsb3RzOiINCmF1dGhvcjogIkRhdmlkIEdyaW5kZXIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciwgZWNobz0gRkFMU0V9DQojSW1wb3J0IGxpYnJhcmllcw0KDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkobGlzdHZpZXdlcikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpgYGANCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCiNJbXBvcnRpbmcgZGF0YQ0KcHJvc19kZiA9IHJlYWRfY3N2KCJwcm9zcGVyMjAxOS5jc3YiKQ0KcHJvc19kZl9zYW0gPSBwcm9zX2RmW3NhbXBsZShucm93KHByb3NfZGYpLDEwMDAwKSxdDQpgYGANCg0KYGBge3J9DQoNCnByb3NfYWdnID0gcHJvc19kZiU+JQ0KICBtdXRhdGUoeXIgPSB5ZWFyKG9yaWdpbmF0aW9uX2RhdGUpLG1uID0gbW9udGgob3JpZ2luYXRpb25fZGF0ZSkpJT4lDQogICNncm91cF9ieSh5cixtbiwgcHJvc3Blcl9yYXRpbmcpJT4lDQogIGdyb3VwX2J5KG9yaWdpbmF0aW9uX2RhdGUscHJvc3Blcl9yYXRpbmcseXIsbW4pJT4lDQogIHN1bW1hcml6ZShhdmdfYW10X2JvcnJvd2VkID0gbWVhbihhbW91bnRfYm9ycm93ZWQpLA0KICAgICAgICAgICAgd19hdmdfcmF0ZSA9IHdlaWdodGVkLm1lYW4oYm9ycm93ZXJfcmF0ZSxhbW91bnRfYm9ycm93ZWQpKSU+JQ0KICBtdXRhdGUob3JnX2R0ID0geW1kKHBhc3RlKHlyLG1uLCcwMScsc2VwID0gJy0nKSkpDQogIA0KcHJvc19hZ2cyID0gcHJvc19kZiU+JQ0KICBtdXRhdGUoeXIgPSB5ZWFyKG9yaWdpbmF0aW9uX2RhdGUpLG1uID0gbW9udGgob3JpZ2luYXRpb25fZGF0ZSkpJT4lDQogIGdyb3VwX2J5KHlyLG1uLCBwcm9zcGVyX3JhdGluZyklPiUNCiAgI2dyb3VwX2J5KG9yaWdpbmF0aW9uX2RhdGUscHJvc3Blcl9yYXRpbmcseXIsbW4pJT4lDQogIHN1bW1hcml6ZShhdmdfYW10X2JvcnJvd2VkID0gbWVhbihhbW91bnRfYm9ycm93ZWQpLA0KICAgICAgICAgICAgd19hdmdfcmF0ZSA9IHdlaWdodGVkLm1lYW4oYm9ycm93ZXJfcmF0ZSxhbW91bnRfYm9ycm93ZWQpKSU+JQ0KICBtdXRhdGUob3JnX2R0ID0geW1kKHBhc3RlKHlyLG1uLCcwMScsc2VwID0gJy0nKSkpDQoNCg0KYGBgDQoNCg0KDQojIEEgbGl0dGxlIGFib3V0IG1lIGJlZm9yZSB3ZSBiZWdpbjoNCg0KDQpNb2RlbGVyIGF0IEJhbmsgb2YgQW1lcmljYQ0KDQpBdmlkIFN0YXIgV2FycyBGYW4NCg0KIVtdKHJldHVybl9vZl9qZWRpLmpwZykNCg0KQW1hdHVlciB1c2VyIGFuZCBhcmRlbnQgc3VwcG9ydGVyIG9mIHZlcnNpb24gY29udHJvbCBpbiBkYXRhIHNjaWVuY2UNCg0KIVtdKGdpdC5wbmcpDQoNClIgdXNlciBmb3IgdGhlIGxhc3QgOSB5ZWFycy4NCg0KVXNlZCBSIGZvciBtb2RlbGluZyBhbmQgZGV2ZWxvcG1lbnQgYXQgQW1lcmljYW4gQ3JlZGl0IEFjY2VwdGFuY2UgYmVmb3JlIGNvbWluZyB0byBCYW5rIG9mIEFtZXJpY2EuDQoNCiFbXShBQ0EucG5nKQ0KDQotIEludHJvZHVjZWQgYW5kIGNoYW1waW9uZWQgU2hpbnkgZm9yIHJhcGlkIGFwcGxpY2F0aW9uIGRldmVsb3BtZW50DQotIERldmVsb3BlZCB2YXJpb3VzIG1vZGVscyBhbmQgcHJvY2VzcyBpbXByb3ZlbWVudHMgdXNpbmcgUiB0b29sIGtpdA0KDQoqKkJhbmsgb2YgQW1lcmljYSB3b3JrOioqDQoNCiFbXShodHRwczovL3d3dy51bmRlcmNvbnNpZGVyYXRpb24uY29tL2JyYW5kbmV3L2FyY2hpdmVzL2Jhbmtfb2ZfYW1lcmljYV9sb2dvX2FuaW1hdGlvbl9uZXdfYS5naWYpDQoNCi0gTW9kZWxpbmcgTGVhZCBmb3IgQ29uc3VtZXIgQmVoYXZpb3IgTW9kZWxpbmc6DQotIFdvcmtlZCBvbiBSaXNrIE1vZGVscyBmb3IgYSB5ZWFyDQotIEN1cnJlbnRseSB3b3JraW5nIG9uIElubm92YXRpb24gdGVhbSAoQmVzdCBqb2IgSSB0aGluayBJIHdpbGwgZXZlciBoYXZlKToNCiAgLSBOZXcgbW9kZWxpbmcgaWRlYXMNCiAgLSBOZXcgYXJlYXMgb2YgdGhlIGJ1c2luZXNzIHRoYXQgd291bGQgYmVuZWZpdCBmcm9tIG1vZGVsaW5nIGtub3dsZWRnZQ0KICAtIEFwcGxpY2F0aW9uIG9mIGRlZXAgbGVhcm5pbmcNCg0KIyAqKldoYXQgaXMgdGhpcyBwcmVzZW5hdGlvbj8qKg0KDQojICoqSSBlbmpveSB0aGUgZGF0YSB2aXN1YWxpemF0aW9uIHNpZGUgb2YgZGF0YSBzY2llbmNlKioNCg0KIyMgKipnZ3Bsb3Qgd2FzIGFsd2F5cyBnbyB0b28gZm9yIG15IGRhdGEgc2NpZW5jZSBuZWVkcyoqDQoNCi0gc2ltcGxlIGludHVpdGl2ZSBzeW50YXggdG8gd3JpdGUgYW5kIHJlYWQNCi0gd2lkZSByYW5nZSBvZiB1c2VmdWwgZ2VvbXMgdG8gc29sdmUgY29tbW9uIG1vZGVsaW5nIHByb2JsZW1zDQotIGludGVncmF0ZXMgd2VsbCB3aXRoIHRoZSByZXN0IG9mIHRpZHl2ZXJzZQ0KDQotIChCaWcgc3RlcCBmb3J3YXJkIGZyb20gbGF0dGljZSBhbmQgYmFzZSBncmFwaGljcykNCg0KDQoNCiMjIyAqKmdncGxvdCBpcyBhbiBhZG9wdGlvbiBvZiB0aGUgcHJlY2VwdHMgbGFpZCBvdXQgaW4gdGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MqKiANCg0KIVtdKEdHX2NvbmNlcHRzLnBuZykNCg0KIyMgZ2dwbG90IGRvZXNuJ3QgbGVuZCBpdHNlbGYgdG8gaW50ZXJhY3Rpdml0eQ0KDQotIHNldmVyYWwgZWZmb3J0cyBoYXZlIGludHJvZHVjZWQgaW50ZXJhY3Rpdml0eSANCiAgLSBnZ3Zpcw0KICAtIHJnZ29iaQ0KICAtIGlQbG90cw0KICAtIGh0bWx3aWRnZXRzDQogIC0gcjJkMw0KDQojIGh0bWx3aWRldHMgYW5kIHIyZDMgYXJlIGFuIGFkYXB0YXRpb24gb2YgRDMvamF2YXNjcmlwdCB0ZWNobm9sb2d5DQoNCiMjIEQzIGlzIGEgamF2YXNjcmlwdCBwYWNrYWdlIHRoYXQgaXMgdmVyeSBpbXBvcnRhbnQgaW4gZGF0YSB2aXN1YWxpemF0aW9uDQoNCi0gV3JpdHRlbiBpbiBhIGxhbmd1YWdlIHRoYXQgaW50ZWdyYXRlcyB3aXRoIHdlYiBkZXZlbG9wbWVudA0KLSBVc2VzIGRhdGEgdG8gbWFuaXB1bGF0ZSB0aGUgd2ViIGRvY3VtZW50IHRocm91Z2ggdmFyaW91cyBvYmplY3RzIChTVkcpDQotIFJlbGVhc2VkIGluIDIwMTENCg0KIyMjIFVzZWZ1bCBleGFtcGxlcyBvZiBwb3dlciBhbmQgaW50ZXJhY3Rpdml0eToNCg0KIyBTdW5idXJzdCBFeGFtcGxlDQoNCiFbXShodHRwczovL2kuc3RhY2suaW1ndXIuY29tL0g2TzJLLmdpZikNCg0KIyBEZW5zaXR5IG92ZXIgdGltZQ0KDQohW10oaHR0cHM6Ly9tZWRpYS5naXBoeS5jb20vbWVkaWEvTlRqaXVza0lNRTZhd0tuMW5EL2dpcGh5LmdpZikNCg0KW0RlY2lzaW9uIFRyZWUgRXhhbXBsZV0oaHR0cDovL2JsLm9ja3Mub3JnL2ZyYWN0YWx5dGljcy9yYXcvNDk1YjYzY2Y2NzFiNGM0ODdiYzQwODAxMzY2Mzg0ZTAvKQ0KDQoNCiMgKipEMyBQcm9zIGFuZCBDb25zKioNCg0KKlByb3MqDQotIFZlcnkgZmxleGlibGUgYW5kIHBvcnRhYmxlDQotIEludGVyYWN0aXZpdHkgcGFydCBvZiB0aGUgZG5hDQotIExvb2tzIHZlcnkgcHJvZmVzc2lvbmFsIGFuZCBwb2xpc2hlZA0KDQoqQ29ucyoNCi0gVmVyeSBzdGVlcCBsZWFybmluZyBjdXJ2ZQ0KICAtIEFQSSByZXF1aXJlcyBkZWNlbnQgdW5kZXJzdGFuZGluZyBvZiBob3cgamF2YXNjcmlwdA0KLSBDZW50ZXJlZCBvbiB3ZWJkZXZlbG9wbWVudCBpbnN0ZWFkIG9mIGRhdGEgc2NpZW5jZQ0KLSA/RmFsbGluZyBvdXQgb2YgZmF2b3I/DQoNCiMjIFdoYXQgaXMgdGhlIGFuc3dlcj8NCg0KIyAqKlBsb3QubHkgaXMgYSBzb2x1dGlvbiB3aXRoIGEgc2ltcGxlciBBUEkgYW5kIG91dCBvZiB0aGUgYm94IGludGVyYWN0aXZpdHkqKg0KDQpFc3NlbnRpYWxseSBwbG90Lmx5IGlzIEFQSSB3cmFwcGVyIGZvciBzZXZlcmFsIEQzDQoNCg0KIyAqKkhvdyBkb2VzIHRoZSBwbG90bHkgcGFja2FnZSB3b3JrKioNCg0KS2V5IHRvIHVuZGVyc3RhZGluZyBwYWNrYWdlIGlzIHVuZGVyc3RhbmRpbmcgaG93IGl0IHRyYW5zZm9ybXMgdGhlIGRhdGENCg0KLSBEYXRhIGVudGVycyBpbiBSIGZvcm1hdHMNCi0gVHJhbnNmb3JtZWQgdG8gbGlzdCBmb3JtYXQgDQotIFRyYW5mb3JtZWQgdG8gSlNPTiBmb3JtYXQNCg0KQmVsb3cgaXMgYSB1c2VmdWwgZGlhZ3JhbSBzaG93aW5nIGhvdyB0aGUgZmluYWwgcHJlc2VudGF0aW9uIGlzIGRvbmUuDQoNCiFbXShwbG90bHlfZGF0YV90cmFuc2Zvcm0uc3ZnKQ0KDQoqKlBsb3RseSB1c2VzIHR3byBrZXkgY29tcG9uZW50czoqKg0KDQoxLiBEYXRhL1RyYWNlOg0KICAtIENvbm5lY3Rpb24gYmV0d2VlbiBkYXRhIGFuZCB2aXN1YWxzDQogIC0gVHJhY2VzIGhhdmUgdHlwZXMgKHNjYXR0ZXIgcGxvdCwgaGlzdG9ncmFtcywgc3VuYnVyc3QsIGV0Yy4pDQogIC0gVHJhY2UgdHlwZXMgaGF2ZSBzcGVjaWZpYyBhdHR0cmlidXRlcyB0aGF0IGNhbiBiZSBkZWZpbmVkLg0KMi4gTGF5b3V0DQoNCg0KIyAqKmdncGxvdGx5KCkgdG8gdGhlIHJlc2N1ZSoqDQoNCklmIHlvdSBoYXZlIHBsb3RzIGluIGdncGxvdCwgeW91IGNhbiBzdGFydCB1c2luZyBwbG90bHkgd2l0aCBqdXN0IGEgc2ltcGxlIGZ1bmN0aW9uIGNhbGwgb24gbW9zdCBnZ3Bsb3Qgb2JqZWN0cy4NCg0KDQpMZXQncyBtYWtlIGEgZ2dwbG90IGZyb20gUHJvcHNlciBkYXRhLiAgDQpgYGB7cn0NCnByb3Nfc2NhdCA9IHByb3NfZGZfc2FtJT4lDQogIGdncGxvdChhZXMocHJpbmNpcGFsX3BhaWQsIGludGVyZXN0X3BhaWQpKStnZW9tX3BvaW50KGFlcyhjb2xvciA9IGZhY3Rvcihwcm9zcGVyX3JhdGluZykpKQ0KDQpwcm9zX3NjYXQNCmBgYA0KDQoNCg0KYGBge3J9DQpnZ3Bsb3RseShwcm9zX3NjYXQpDQoNCmBgYA0KDQpgYGB7cn0NCg0KDQpwcm9zX2RmX3NhbSU+JQ0KICBwbG90X2x5KHggPSB+cHJpbmNpcGFsX3BhaWQsIHkgPSB+aW50ZXJlc3RfcGFpZCklPiUNCiAgYWRkX21hcmtlcnMoY29sb3IgPSB+cHJvc3Blcl9yYXRpbmcpDQpgYGANCg0KDQoNCkRvIGl0IGFnYWluLCBidXQgd2l0aCBsaW5lczoNCmBgYHtyfQ0KYW10X2Zpbl9wID0gcHJvc19hZ2clPiUNCiAgZ2dwbG90KGFlcyhvcmlnaW5hdGlvbl9kYXRlLGF2Z19hbXRfYm9ycm93ZWQpKSsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IHByb3NwZXJfcmF0aW5nKSkNCg0KYW10X2Zpbl9wDQpgYGANCg0KU2ltcGxlIGdncGxvdGx5IGNvbW1hbmQgYWRkcyB0aGUgdG9vbHRpcCwgem9vbWluZywgDQoNCmBgYHtyfQ0KZ2dwbG90bHkoYW10X2Zpbl9wKQ0KYGBgDQoNCk5vdyB0aGUgcGxvdGx5IHN5bnRheDoNCg0KYGBge3J9DQpwcm9zX2FnZyU+JQ0KICB1bmdyb3VwKCklPiUNCiAgcGxvdF9seSh4ID0gfm9yaWdpbmF0aW9uX2RhdGUsIHkgPSB+YXZnX2FtdF9ib3Jyb3dlZCwgY29sb3IgPSB+cHJvc3Blcl9yYXRpbmcsIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnKQ0KIyAgYWRkX2xpbmVzKGNvbG9yID0gfnByb3NwZXJfcmF0aW5nKQ0KYGBgDQpTYW1lIEdyYXBoIGRpZmZlcmVudCBzeW50YXggKHVzaW5nIHRoZSBhZGRfKiB0cmFjZSBhZGRpdGlvbikNCmBgYHtyfQ0KcHJvc19hZ2clPiUNCiAgdW5ncm91cCgpJT4lDQogIHBsb3RfbHkoeCA9IH5vcmlnaW5hdGlvbl9kYXRlLCB5ID0gfmF2Z19hbXRfYm9ycm93ZWQpJT4lDQogIGFkZF9saW5lcyhjb2xvciA9IH5wcm9zcGVyX3JhdGluZykNCmBgYA0KDQpIaXN0b2dyYW06DQpgYGB7cn0NCnByb3NfZGZfc2FtJT4lDQogIHBsb3RfbHkoeCA9IH5ib3Jyb3dlcl9yYXRlKSU+JQ0KICBhZGRfaGlzdG9ncmFtKCkNCmBgYA0KDQpIaXN0b2dyYW0gd2l0aCBtdWlsdHBsZSBmYWN0b3JzDQpgYGB7cn0NCg0KcHJvc19kZl9zYW0lPiUNCiAgcGxvdF9seSh4ID0gfmZhY3Rvcih0ZXJtKSwgY29sb3IgPSB+cHJvc3Blcl9yYXRpbmcpJT4lDQogIGFkZF9oaXN0b2dyYW0oKQ0KDQpgYGANCg0KTm93IGxldHMgZG8gdGhpcyB3aXRoIDIgZGltZW50aW9ucw0KDQpgYGB7cn0NCg0KcHJvc19kZiU+JQ0KICBwbG90X2x5KA0KICAgIHggPSB+cHJpbmNpcGFsX3BhaWQsDQogICAgeSA9IH5pbnRlcmVzdF9wYWlkLA0KICAgIHR5cGUgPSAnaGlzdG9ncmFtMmRjb250b3VyJw0KICApDQogIA0KICANCiAgDQpgYGANCg0KYGBge3J9DQoNCnN1YnBsdCA9IHN1YnBsb3QoDQogIA0KICBwcm9zX2RmX3NhbSU+JXBsb3RfbHkoeCA9IH5wcmluY2lwYWxfcGFpZCwgY29sb3IgPSBJKCJibGFjayIpLHR5cGUgPSAnaGlzdG9ncmFtJyksDQogIHBsb3RseV9lbXB0eSgpLA0KICBwcm9zX2RmX3NhbSU+JXBsb3RfbHkoeCA9IH5wcmluY2lwYWxfcGFpZCx5ID0gfmludGVyZXN0X3BhaWQsdHlwZSA9ICdoaXN0b2dyYW0yZGNvbnRvdXInKSwNCiAgcHJvc19kZl9zYW0lPiVwbG90X2x5KHkgPSB+aW50ZXJlc3RfcGFpZCxjb2xvciA9IEkoImJsYWNrIiksdHlwZSA9ICdoaXN0b2dyYW0nKSwNCiAgbnJvd3MgPSAyLCAgDQogIGhlaWdodHMgPSBjKDAuMiwwLjgpLCANCiAgd2lkdGhzID0gYygwLjgsMC4yKSwNCiAgc2hhcmVYID0gVFJVRSwNCiAgc2hhcmVZID0gVFJVRQ0KKQ0KDQpwID0gbGF5b3V0KHN1YnBsdCwgc2hvd2xlZ2VuZCA9IEZBTFNFKQ0KICANCnANCiAgDQpgYGANCg0KDQoNCg0KYGBge3J9DQp4IDwtIHJub3JtKDEwMDApDQp5IDwtIHJub3JtKDEwMDApDQpzIDwtIHN1YnBsb3QoDQogIHBsb3RfbHkoeCA9IHgsIGNvbG9yID0gSSgiYmxhY2siKSwgdHlwZSA9ICdoaXN0b2dyYW0nKSwgDQogIHBsb3RseV9lbXB0eSgpLCANCiAgcGxvdF9seSh4ID0geCwgeSA9IHksIHR5cGUgPSAnaGlzdG9ncmFtMmRjb250b3VyJywgc2hvd3NjYWxlID0gRiksIA0KICBwbG90X2x5KHkgPSB5LCBjb2xvciA9IEkoImJsYWNrIiksIHR5cGUgPSAnaGlzdG9ncmFtJyksDQogIG5yb3dzID0gMiwgaGVpZ2h0cyA9IGMoMC4yLCAwLjgpLCB3aWR0aHMgPSBjKDAuOCwgMC4yKSwgDQogIHNoYXJlWCA9IFRSVUUsIHNoYXJlWSA9IFRSVUUsIHRpdGxlWCA9IEZBTFNFLCB0aXRsZVkgPSBGQUxTRQ0KKQ0KDQpwIDwtIGxheW91dChzLCBzaG93bGVnZW5kID0gRkFMU0UpDQoNCnANCmBgYA0KDQoNCldoYXQgZG9lcyB0aGF0IGxvb2sgbGlrZSBpbiBnZ3Bsb3Q/ICBEb2FibGUgYnV0IG5vdCBpbnR1aXRpdmUuDQpgYGB7cn0NCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KDQpoaXN0X3RvcCA8LSBwcm9zX2RmX3NhbSU+JWdncGxvdChhZXMocHJpbmNpcGFsX3BhaWQpKStnZW9tX2hpc3RvZ3JhbSgpDQplbXB0eSA8LSBnZ3Bsb3QoKStnZW9tX3BvaW50KGFlcygxLDEpLCBjb2xvdXI9IndoaXRlIikrDQogICAgICAgICB0aGVtZShheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSwgDQogICAgICAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSwgDQogICAgICAgICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwgICAgICAgICAgIA0KICAgICAgICAgICAgICAgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSkNCg0Kc2NhdHRlciA8LSBnZ3Bsb3QoKStnZW9tX2RlbnNpdHlfMmQoYWVzKHByb3NfZGZfc2FtJHByaW5jaXBhbF9iYWxhbmNlLCBwcm9zX2RmX3NhbSRpbnRlcmVzdF9wYWlkKSkNCg0KaGlzdF9yaWdodCA8LSBwcm9zX2RmX3NhbSU+JWdncGxvdChhZXMoaW50ZXJlc3RfcGFpZCkpK2dlb21faGlzdG9ncmFtKCkrY29vcmRfZmxpcCgpDQoNCmdyaWQuYXJyYW5nZShoaXN0X3RvcCwgZW1wdHksIHNjYXR0ZXIsIGhpc3RfcmlnaHQsIG5jb2w9MiwgbnJvdz0yLCB3aWR0aHM9Yyg0LCAxKSwgaGVpZ2h0cz1jKDEsIDQpKQ0KYGBgDQoNCkFjay4gIE5vdCBhcyBnb29kLiAgDQoNCg0KTm93IHNvbWUgcGxvdHMgdGhhdCBhcmUgdmVyeSBkaWZmaWN1bHQgdG8gZG8gaW4gZ2dwbG90IGFuZCByZWx5IG9uIHRoZSBpbnRlcmFjdGl2aXR5IGhlYXZpbHkuDQoNClN1bmJ1cnN0DQoNCkZpcnN0IHN0ZXAgdG8gbWFraW5nIHRoaXMgb25lIHdvcmsgaXMgcHJvY2Vzc2luZyB0aGUgZGF0YToNCg0KTXVzdCBoYXZlIGxhYmVscywgcGFyZW50cyBhbmQgdmFsdWVzLg0KDQpXaWxsIHVzZSB0aGUgVGl0YW5pYyBkYXRhc2V0IGJlY2F1c2UgaXQgaGFzIHNvbWUgbmljZSBjYXRlZ29yaWVzIGFuZCB2YWx1ZXMgdG8gdGFrZSBvbjoNCg0KYGBge3J9DQoNCmxpYnJhcnkoZGF0YXNldHMpDQoNCnRpdGFuaWNfZGYgPSBhcy5kYXRhLmZyYW1lKFRpdGFuaWMpDQoNCnRpdGFuaWNfZGYNCmBgYA0KDQoNCmBgYHtyfQ0KYWdnMSA9IHRpdGFuaWNfZGYlPiUNCiAgZ3JvdXBfYnkoU3Vydml2ZWQpJT4lDQogIHN1bW1hcml6ZSh2YWx1ZSA9IHN1bShGcmVxKSklPiUNCiAgbXV0YXRlKGlkID0gYXMuY2hhcmFjdGVyKFN1cnZpdmVkKSwNCiAgICAgICAgIGxhYmVsID0gYXMuY2hhcmFjdGVyKFN1cnZpdmVkKSwNCiAgICAgICAgIHBhcmVudCA9ICIiKSU+JQ0KICBzZWxlY3QobGFiZWwsIGlkLCBwYXJlbnQsIHZhbHVlKQ0KDQphZ2cyID0gdGl0YW5pY19kZiU+JQ0KICBncm91cF9ieShTdXJ2aXZlZCxDbGFzcyklPiUNCiAgc3VtbWFyaXplKHZhbHVlID0gc3VtKEZyZXEpKSU+JQ0KICB1bmdyb3VwKCklPiUNCiAgbXV0YXRlKGlkID0gcGFzdGUoU3Vydml2ZWQsIENsYXNzLCBzZXAgPSAnIC0gJyksDQogICAgICAgICBwYXJlbnQgPSBhcy5jaGFyYWN0ZXIoU3Vydml2ZWQpLA0KICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoQ2xhc3MpKSU+JQ0KICBzZWxlY3QobGFiZWwsIGlkLCBwYXJlbnQsIHZhbHVlKQ0KDQphZ2czID0gdGl0YW5pY19kZiU+JQ0KICBncm91cF9ieShTdXJ2aXZlZCwgQ2xhc3MsIEFnZSklPiUNCiAgc3VtbWFyaXplKHZhbHVlID0gc3VtKEZyZXEpKSU+JQ0KICB1bmdyb3VwKCklPiUNCiAgbXV0YXRlKGlkID0gcGFzdGUoU3Vydml2ZWQsIENsYXNzLCBBZ2UsIHNlcCA9ICcgLSAnKSwNCiAgICAgICAgIHBhcmVudCA9IHBhc3RlKFN1cnZpdmVkLCBDbGFzcywgc2VwID0gJyAtICcpLA0KICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoQWdlKSklPiUNCiAgc2VsZWN0KGxhYmVsLCBpZCwgcGFyZW50LCB2YWx1ZSkNCg0KDQphZ2c0ID0gdGl0YW5pY19kZiU+JQ0KICBncm91cF9ieShTdXJ2aXZlZCxDbGFzcyxBZ2UsU2V4KSU+JQ0KICBzdW1tYXJpemUodmFsdWUgPSBzdW0oRnJlcSkpJT4lDQogIHVuZ3JvdXAoKSU+JQ0KICBtdXRhdGUoaWQgPSBwYXN0ZShTdXJ2aXZlZCwgQ2xhc3MsIEFnZSwgU2V4LCBzZXAgPSAnIC0gJyksDQogICAgICAgICBwYXJlbnQgPSBwYXN0ZShTdXJ2aXZlZCwgQ2xhc3MsIEFnZSwgc2VwID0gJyAtICcpLA0KICAgICAgICAgbGFiZWwgPSBhcy5jaGFyYWN0ZXIoU2V4KSklPiUNCiAgc2VsZWN0KGxhYmVsLCBpZCwgcGFyZW50LCB2YWx1ZSkNCg0KDQphZ2cgPWJpbmRfcm93cyhhZ2cxLGFnZzIpJT4lYmluZF9yb3dzKGFnZzMpJT4lYmluZF9yb3dzKGFnZzQpJT4lZmlsdGVyKHZhbHVlID4gMCkNCg0KDQoNCmFnZw0KYGBgDQoNCmBgYHtyfQ0KDQoNCnBsb3RfbHkoYWdnLA0KICAgICAgICBpZHMgPSB+aWQsDQogICAgICAgIGxhYmVscyA9IH5sYWJlbCwNCiAgICAgICAgcGFyZW50cyA9IH5wYXJlbnQsDQogICAgICAgIHZhbHVlcyA9IH52YWx1ZSwNCiAgICAgICAgdHlwZSA9ICdzdW5idXJzdCcsDQogICAgICAgIGJyYW5jaHZhbHVlcz0gJ3RvdGFsJyklPiUNCiAgbGF5b3V0KHRpdGxlID0gJ0JyZWFrZG93biBvZiBUaXRhbCBzdXJ2aXZhbCBieSBDbGFzcywgQWdlLCBhbmQgU2V4JyApDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KHBsb3RseSkNCg0KZCA8LSBkYXRhLmZyYW1lKA0KICAgIGlkcyA9IGMoDQogICAgIk5vcnRoIEFtZXJpY2EiLCAiRXVyb3BlIiwgIkF1c3RyYWxpYSIsICJOb3J0aCBBbWVyaWNhIC0gRm9vdGJhbGwiLCAiU29jY2VyIiwNCiAgICAiTm9ydGggQW1lcmljYSAtIFJ1Z2J5IiwgIkV1cm9wZSAtIEZvb3RiYWxsIiwgIlJ1Z2J5IiwNCiAgICAiRXVyb3BlIC0gQW1lcmljYW4gRm9vdGJhbGwiLCJBdXN0cmFsaWEgLSBGb290YmFsbCIsICJBc3NvY2lhdGlvbiIsDQogICAgIkF1c3RyYWxpYW4gUnVsZXMiLCAiQXV0c3RyYWxpYSAtIEFtZXJpY2FuIEZvb3RiYWxsIiwgIkF1c3RyYWxpYSAtIFJ1Z2J5IiwNCiAgICAiUnVnYnkgTGVhZ3VlIiwgIlJ1Z2J5IFVuaW9uIg0KICApLA0KICBsYWJlbHMgPSBjKA0KICAgICJOb3J0aDxicj5BbWVyaWNhIiwgIkV1cm9wZSIsICJBdXN0cmFsaWEiLCAiRm9vdGJhbGwiLCAiU29jY2VyIiwgIlJ1Z2J5IiwNCiAgICAiRm9vdGJhbGwiLCAiUnVnYnkiLCAiQW1lcmljYW48YnI+Rm9vdGJhbGwiLCAiRm9vdGJhbGwiLCAiQXNzb2NpYXRpb24iLA0KICAgICJBdXN0cmFsaWFuPGJyPlJ1bGVzIiwgIkFtZXJpY2FuPGJyPkZvb3RiYWxsIiwgIlJ1Z2J5IiwgIlJ1Z2J5PGJyPkxlYWd1ZSIsDQogICAgIlJ1Z2J5PGJyPlVuaW9uIg0KICApLA0KICBwYXJlbnRzID0gYygNCiAgICAiIiwgIiIsICIiLCAiTm9ydGggQW1lcmljYSIsICJOb3J0aCBBbWVyaWNhIiwgIk5vcnRoIEFtZXJpY2EiLCAiRXVyb3BlIiwNCiAgICAiRXVyb3BlIiwgIkV1cm9wZSIsIkF1c3RyYWxpYSIsICJBdXN0cmFsaWEgLSBGb290YmFsbCIsICJBdXN0cmFsaWEgLSBGb290YmFsbCIsDQogICAgIkF1c3RyYWxpYSAtIEZvb3RiYWxsIiwgIkF1c3RyYWxpYSAtIEZvb3RiYWxsIiwgIkF1c3RyYWxpYSAtIFJ1Z2J5IiwNCiAgICAiQXVzdHJhbGlhIC0gUnVnYnkiDQogICksDQogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KKQ0KDQpwbG90X2x5KGQsIGlkcyA9IH5pZHMsIGxhYmVscyA9IH5sYWJlbHMsIHBhcmVudHMgPSB+cGFyZW50cywgdHlwZSA9ICdzdW5idXJzdCcpJT4lDQpgYGANCg0KDQpgYGB7cn0NCmxpYnJhcnkocmpzb24pDQoNCmpzb25fZmlsZSA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Bsb3RseS9wbG90bHkuanMvbWFzdGVyL3Rlc3QvaW1hZ2UvbW9ja3Mvc2Fua2V5X2VuZXJneS5qc29uIg0KanNvbl9kYXRhIDwtIGZyb21KU09OKHBhc3RlKHJlYWRMaW5lcyhqc29uX2ZpbGUpLCBjb2xsYXBzZT0iIikpDQoNCnAgPC0gcGxvdF9seSgNCiAgICB0eXBlID0gInNhbmtleSIsDQogICAgZG9tYWluID0gbGlzdCgNCiAgICAgIHggPSAgYygwLDEpLA0KICAgICAgeSA9ICBjKDAsMSkNCiAgICApLA0KICAgIG9yaWVudGF0aW9uID0gImgiLA0KICAgIHZhbHVlZm9ybWF0ID0gIi4wZiIsDQogICAgdmFsdWVzdWZmaXggPSAiVFdoIiwNCg0KICAgIG5vZGUgPSBsaXN0KA0KICAgICAgbGFiZWwgPSBqc29uX2RhdGEkZGF0YVtbMV1dJG5vZGUkbGFiZWwsDQogICAgICBjb2xvciA9IGpzb25fZGF0YSRkYXRhW1sxXV0kbm9kZSRjb2xvciwNCiAgICAgIHBhZCA9IDE1LA0KICAgICAgdGhpY2tuZXNzID0gMTUsDQogICAgICBsaW5lID0gbGlzdCgNCiAgICAgICAgY29sb3IgPSAiYmxhY2siLA0KICAgICAgICB3aWR0aCA9IDAuNQ0KICAgICAgKQ0KICAgICksDQoNCiAgICBsaW5rID0gbGlzdCgNCiAgICAgIHNvdXJjZSA9IGpzb25fZGF0YSRkYXRhW1sxXV0kbGluayRzb3VyY2UsDQogICAgICB0YXJnZXQgPSBqc29uX2RhdGEkZGF0YVtbMV1dJGxpbmskdGFyZ2V0LA0KICAgICAgdmFsdWUgPSAganNvbl9kYXRhJGRhdGFbWzFdXSRsaW5rJHZhbHVlLA0KICAgICAgbGFiZWwgPSAganNvbl9kYXRhJGRhdGFbWzFdXSRsaW5rJGxhYmVsDQogICAgKQ0KICApICU+JSANCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gIkVuZXJneSBmb3JlY2FzdCBmb3IgMjA1MDxicj5Tb3VyY2U6IERlcGFydG1lbnQgb2YgRW5lcmd5ICYgQ2xpbWF0ZSBDaGFuZ2UsIFRvbSBDb3Vuc2VsbCB2aWEgPGEgaHJlZj0naHR0cHM6Ly9ib3N0Lm9ja3Mub3JnL21pa2Uvc2Fua2V5Lyc+TWlrZSBCb3N0b2NrPC9hPiIsDQogICAgZm9udCA9IGxpc3QoDQogICAgICBzaXplID0gMTANCiAgICApLA0KICAgIHhheGlzID0gbGlzdChzaG93Z3JpZCA9IEYsIHplcm9saW5lID0gRiksDQogICAgeWF4aXMgPSBsaXN0KHNob3dncmlkID0gRiwgemVyb2xpbmUgPSBGKQ0KKQ0KDQpwDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=